#!/usr/bin/python
#
# Copyright (c) 2009 Julius Volz
# See LICENSE for details.

import config, dataset, debug, output, time
from format import text, html

class LinkSpec:
  def __init__(self, id, link_type, src_ds, dst_ds, restrict, metric, filter,
               thresholds, output_opts, sync_opts):
    self.id = id
    self.link_type = link_type
    self.src_ds = src_ds
    self.dst_ds = dst_ds
    self.restrict = restrict
    self.metric = metric
    self.filter = filter
    self.thresholds = thresholds
    self.output = output_opts
    self.sync = sync_opts
    self.links = {}
    self.initialized = False

    if output_opts['format'] not in output.formats:
      raise config.Error('Invalid output format "%s"' % output_opts['format'])

  def init(self):
    # If applicable, build and use index over dest resources
    if self.restrict is not None:
      debug.log(2, 'Using index for looking up destination resources.\n')
      import index
      self.dst_index = index.Index(self.dst_ds, self.restrict['dst_indexes'])
    else:
      self.dst_index = None
      debug.log(2, 'No index, using all destination resources for comparisons.\n')
      debug.log(2, "Loading destination resources...")
      self.dst_resources = self.dst_ds.resources()
      debug.log(2, "found %s.\n" % len(self.dst_resources))

    debug.log(2, "Loading source resources...")
    self.src_resources = self.src_ds.resources()
    debug.log(2, "found %s.\n" % len(self.src_resources))

    self.initialized = True

  def run(self, sync_daemon=False):
    if not self.initialized:
      self.init()

    # Perform actual comparisons and create links
    self.compare_and_link()

    if self.filter is not None:
      self.filter_links()

    self.accept_links = []
    self.verify_links = []

    for links in self.links.values():
      for link in links:
        if link['similarity'] > self.thresholds['accept']:
          self.accept_links.append(link)
        elif link['similarity'] > self.thresholds['verify']:
          self.verify_links.append(link)

    self.write_links()

    if sync_daemon:
      from sync.source import sync
      sync.add_links(self.id, self.accept_links, self.link_type)


  def validate_link(self, src_resource, dst_resource):
    if not self.initialized:
      self.init()

    return self.compare_pair(src_resource, dst_resource) > self.thresholds['accept']


  def compare_and_link(self):
    debug.log(2, "Linking...\n")

    if self.dst_index is not None:
      src_restrict_path = self.restrict['src']

    num_comparisons = 0
    for src_resource in self.src_resources:
      if self.dst_index is not None:
        dst_resources = self.dst_index.search(src_restrict_path,
                                              src_resource,
                                              self.src_ds,
                                              self.restrict['limit'])
      else:
        dst_resources = self.dst_resources

      for dst_resource in dst_resources:
        if config.verbosity >= 4:
          trace = {}
        else:
          trace = None
        similarity = self.compare_pair(src_resource, dst_resource, trace)

        if similarity > self.thresholds['verify']:
          self.add_link(src_resource, dst_resource, similarity)

        num_comparisons += 1

        if num_comparisons % 100 == 0:
          print '%s %s' % (time.time(), num_comparisons)

  def add_link(self, src_resource, dst_resource, similarity):
    if src_resource not in self.links:
      self.links[src_resource] = []
    
    debug.log(6, 'ADDING LINK: <%s> %s <%s>\n' % (src_resource, self.link_type, dst_resource))
    self.links[src_resource].append({'src': src_resource,
                                     'dst': dst_resource,
                                     'similarity': similarity})

  def filter_links(self):
    debug.log(2, "Applying link limit...\n")
    limit = self.filter['limit']
    sort_func = lambda x, y: x['similarity'] < y['similarity'] and 1 or -1
    for src in self.links:
      self.links[src].sort(sort_func)
      self.links[src] = self.links[src][:limit]

    rev_links = {}
    for src in self.links:
      for link in self.links[src]:
        if link['dst'] not in rev_links:
          rev_links[link['dst']] = []
        rev_links[link['dst']].append(link)

    for dst in rev_links:
      rev_links[dst].sort(sort_func)
      rev_links[dst] = rev_links[dst][:limit]

    self.links = rev_links

  def write_links(self):
    # TODO: error handling, unspecified files, etc.
    accept_file = open(self.output['accept'], self.output['mode'])
    verify_file = open(self.output['verify'], self.output['mode'])

    output.output(self.accept_links, self.link_type, self.output['namespace'],
                  accept_file, self.output['format'])
    output.output(self.verify_links, self.link_type, self.output['namespace'],
                  verify_file, self.output['format'])

    accept_file.close()
    verify_file.close()

  def compare_pair(self, src_resource, dst_resource, trace=None):
    debug.log_indent(3, "\nComparing '%s' and '%s':\n" % (src_resource, dst_resource))
    # setup variable bindings for source and destination datasets
    bindings = {
      self.src_ds.var: [{
        'type': 'uri',
        'value': src_resource,
        'dataset': self.src_ds
      }],
      self.dst_ds.var: [{
        'type': 'uri',
        'value': dst_resource,
        'dataset': self.dst_ds
      }]
    }

    similarity = self.metric.evaluate(bindings, trace)
    if trace is not None:
      trace['value'] = similarity

      if config.verbosity >= 4:
        debug.log(4, text.format([trace]))

    debug.log_outdent(3, "Total similarity: %s\n" % similarity)
    return similarity

  def find_matches(self, src_resource):
    #var = self.src_ds.var # TODO: hack
    #self.src_ds = dataset.CrawlerDataset(src_resource, var)

    if not self.initialized:
      self.init()

    self.src_resources = self.src_ds.resources()

    # Perform actual comparisons and create links
    self.compare_and_link()

    if self.filter is not None:
      self.filter_links()

    return self.links
